/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#ifndef NODE_DB_SQL_STATEMENT_H_
#define NODE_DB_SQL_STATEMENT_H_

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif

#include <cstdint>
#include <exception>
#include <string>
#include <vector>
#include <sstream>
#include "node/assert.h"

namespace node {
    namespace db {
        class Connection;

        union SqlStmtField
        {
            bool boolean;
            ::std::uint8_t ui8;
            ::std::int8_t i8;
            ::std::uint16_t ui16;
            ::std::int16_t i16;
            ::std::uint32_t ui32;
            ::std::int32_t i32;
            ::std::uint64_t ui64;
            ::std::int64_t i64;
            float f;
            double d;
        };

        enum SqlStmtFieldType
        {
            FIELD_BOOL,
            FIELD_UI8,
            FIELD_UI16,
            FIELD_UI32,
            FIELD_UI64,
            FIELD_I8,
            FIELD_I16,
            FIELD_I32,
            FIELD_I64,
            FIELD_FLOAT,
            FIELD_DOUBLE,
            FIELD_STRING,
            FIELD_NONE
        };

        class SqlStmtFieldData
        {
        public:
            SqlStmtFieldData() : type_(FIELD_NONE) { binary_data_.ui64 = 0; }
            ~SqlStmtFieldData() {}

            template<typename T>
            SqlStmtFieldData(T param) { set(param); }

            template<typename T1>
            void set(T1 param1);


            bool toBool() const { NODE_ASSERT(type_ == FIELD_BOOL); return binary_data_.ui8 != 0; }
            ::std::uint8_t toUint8() const { NODE_ASSERT(type_ == FIELD_UI8); return binary_data_.ui8; }
            ::std::int8_t toInt8() const { NODE_ASSERT(type_ == FIELD_I8); return binary_data_.i8; }
            ::std::uint16_t toUint16() const { NODE_ASSERT(type_ == FIELD_UI16); return binary_data_.ui16; }
            ::std::int16_t toInt16() const { NODE_ASSERT(type_ == FIELD_I16); return binary_data_.i16; }
            ::std::uint32_t toUint32() const { NODE_ASSERT(type_ == FIELD_UI32); return binary_data_.ui32; }
            ::std::int32_t toInt32() const { NODE_ASSERT(type_ == FIELD_I32); return binary_data_.i32; }
            ::std::uint64_t toUint64() const { NODE_ASSERT(type_ == FIELD_UI64); return binary_data_.ui64; }
            ::std::int64_t toInt64() const { NODE_ASSERT(type_ == FIELD_I64); return binary_data_.i64; }
            float toFloat() const { NODE_ASSERT(type_ == FIELD_FLOAT); return binary_data_.f; }
            double toDouble() const { NODE_ASSERT(type_ == FIELD_DOUBLE); return binary_data_.d; }
            const char* toStr() const { NODE_ASSERT(type_ == FIELD_STRING); return string_data_.c_str(); }

            SqlStmtFieldType type() const { return type_; }
            void* buff() const { return type_ == FIELD_STRING ? (void*)string_data_.c_str() : (void*)&binary_data_; }
            size_t size() const
            {
                switch (type_)
                {
                    case FIELD_NONE:    return 0;
                    case FIELD_BOOL:    // return sizeof(bool);
                    case FIELD_UI8:     return sizeof(::std::uint8_t);
                    case FIELD_UI16:    return sizeof(::std::uint16_t);
                    case FIELD_UI32:    return sizeof(::std::uint32_t);
                    case FIELD_UI64:    return sizeof(::std::uint64_t);
                    case FIELD_I8:      return sizeof(::std::int8_t);
                    case FIELD_I16:     return sizeof(::std::int16_t);
                    case FIELD_I32:     return sizeof(::std::int32_t);
                    case FIELD_I64:     return sizeof(::std::int64_t);
                    case FIELD_FLOAT:   return sizeof(float);
                    case FIELD_DOUBLE:  return sizeof(double);
                    case FIELD_STRING:  return string_data_.length();

                    default:
                        throw std::runtime_error("unrecognized type of SqlStmtFieldType obtained");
                }
            }

        private:
            SqlStmtFieldType type_;
            SqlStmtField binary_data_;
            ::std::string string_data_;
        };

        template<> inline void SqlStmtFieldData::set(bool val) { type_ = FIELD_BOOL; binary_data_.ui8 = val; }
        template<> inline void SqlStmtFieldData::set(::std::uint8_t val) { type_ = FIELD_UI8; binary_data_.ui8 = val; }
        template<> inline void SqlStmtFieldData::set(::std::int8_t val) { type_ = FIELD_I8; binary_data_.i8 = val; }
        template<> inline void SqlStmtFieldData::set(::std::uint16_t val) { type_ = FIELD_UI16; binary_data_.ui16 = val; }
        template<> inline void SqlStmtFieldData::set(::std::int16_t val) { type_ = FIELD_I16; binary_data_.i16 = val; }
        template<> inline void SqlStmtFieldData::set(::std::uint32_t val) { type_ = FIELD_UI32; binary_data_.ui32 = val; }
        template<> inline void SqlStmtFieldData::set(::std::int32_t val) { type_ = FIELD_I32; binary_data_.i32 = val; }
        template<> inline void SqlStmtFieldData::set(::std::uint64_t val) { type_ = FIELD_UI64; binary_data_.ui64 = val; }
        template<> inline void SqlStmtFieldData::set(::std::int64_t val) { type_ = FIELD_I64; binary_data_.i64 = val; }
        template<> inline void SqlStmtFieldData::set(float val) { type_ = FIELD_FLOAT; binary_data_.f = val; }
        template<> inline void SqlStmtFieldData::set(double val) { type_ = FIELD_DOUBLE; binary_data_.d = val; }
        template<> inline void SqlStmtFieldData::set(const char* val) { type_ = FIELD_STRING; string_data_ = val; }

        class SqlStatement;
        class SqlStmtParameters
        {
        public:
            typedef ::std::vector<SqlStmtFieldData> ParameterContainer;
            explicit SqlStmtParameters(::std::uint32_t nParams);

            ~SqlStmtParameters() {}
            ::std::uint32_t boundParams() const { return params_.size(); }
            void addParam(const SqlStmtFieldData& data) { params_.push_back(data); }
            void reset(const SqlStatement& stmt);
            void swap(SqlStmtParameters& obj);
            const ParameterContainer& params() const { return params_; }

        private:
            SqlStmtParameters& operator=(const SqlStmtParameters& obj);
            ParameterContainer params_;
        };

        class SqlStatementID
        {
        public:
            SqlStatementID() : initialized_(false) {}
            int id() const { return index_; }
            ::std::uint32_t arguments() const { return arguments_; }
            bool initialized() const { return initialized_; }

        private:
            friend class Database;
            void init(int nID, ::std::uint32_t nArgs) { index_ = nID; arguments_ = nArgs; initialized_ = true; }

            int index_;
            ::std::uint32_t arguments_;
            bool initialized_;
        };

        class SqlStatement
        {
        public:
            ~SqlStatement() { delete params_; }

            SqlStatement(const SqlStatement& index) : index_(index.index_), db_(index.db_), params_(NULL)
            {
                if (index.params_)
                { params_ = new SqlStmtParameters(*(index.params_)); }
            }

            SqlStatement& operator=(const SqlStatement& index);
            int id() const { return index_.id(); }
            ::std::uint32_t arguments() const { return index_.arguments(); }
            bool execute();
            bool directExecute();

            template<typename ParamType1>
            bool pexecute(ParamType1 param1)
            {
                arg(param1);
                return execute();
            }

            template<typename ParamType1, typename ParamType2>
            bool pexecute(ParamType1 param1, ParamType2 param2)
            {
                arg(param1);
                arg(param2);
                return execute();
            }

            template<typename ParamType1, typename ParamType2, typename ParamType3>
            bool pexecute(ParamType1 param1, ParamType2 param2, ParamType3 param3)
            {
                arg(param1);
                arg(param2);
                arg(param3);
                return execute();
            }

            template<typename ParamType1, typename ParamType2, typename ParamType3, typename ParamType4>
            bool pexecute(ParamType1 param1, ParamType2 param2, ParamType3 param3, ParamType4 param4)
            {
                arg(param1);
                arg(param2);
                arg(param3);
                arg(param4);
                return execute();
            }

            void addBool(bool var) { arg(var); }
            void addUInt8(::std::uint8_t var) { arg(var); }
            void addInt8(::std::int8_t var) { arg(var); }
            void addUInt16(::std::uint16_t var) { arg(var); }
            void addInt16(::std::int16_t var) { arg(var); }
            void addUInt32(::std::uint32_t var) { arg(var); }
            void addInt32(::std::int32_t var) { arg(var); }
            void addUInt64(::std::uint64_t var) { arg(var); }
            void addInt64(::std::int64_t var) { arg(var); }
            void addFloat(float var) { arg(var); }
            void addDouble(double var) { arg(var); }
            void addString(const char* var) { arg(var); }
            void addString(const std::string& var) { arg(var.c_str()); }
            void addString(std::ostringstream& ss) { arg(ss.str().c_str()); ss.str(std::string()); }

        protected:
            friend class Database;
            SqlStatement(const SqlStatementID& index, Database& db) : index_(index), db_(&db), params_(NULL) {}

        private:
            SqlStmtParameters* get()
            {
                if (!params_)
                { params_ = new SqlStmtParameters(arguments()); }

                return params_;
            }

            SqlStmtParameters* detach()
            {
                SqlStmtParameters* p = params_ ? params_ : new SqlStmtParameters(0);
                params_ = NULL;
                return p;
            }

            template<typename ParamType>
            void arg(ParamType val)
            {
                SqlStmtParameters* p = get();
                p->addParam(SqlStmtFieldData(val));
            }

            SqlStatementID index_;
            Database* db_;
            SqlStmtParameters* params_;
        };

        class SqlPreparedStatement
        {
        public:
            virtual ~SqlPreparedStatement() {}
            bool isPrepared() const { return prepared_; }
            bool isQuery() const { return query_; }
            ::std::uint32_t params() const { return params_; }
            ::std::uint32_t columns() const { return isQuery() ? columns_ : 0; }
            virtual bool prepare() = 0;
            virtual void bind(const SqlStmtParameters& holder) = 0;
            virtual bool execute() = 0;

        protected:
            SqlPreparedStatement(const std::string& fmt, Connection& conn) :
                 params_(0), columns_(0), query_(false),
                     prepared_(false), format_(fmt), conn_(conn)
                 {}

             ::std::uint32_t params_;
             ::std::uint32_t columns_;
             bool query_;
             bool prepared_;
             std::string format_;
             Connection& conn_;
        };

        class SqlPlainPreparedStatement : public SqlPreparedStatement
        {
        public:
            SqlPlainPreparedStatement(const std::string& fmt, Connection& conn);
            ~SqlPlainPreparedStatement() {}
            virtual bool prepare() override { return true; }
            virtual void bind(const SqlStmtParameters& holder) override;
            virtual bool execute() override;

        protected:
            void dataToString(const SqlStmtFieldData& data, std::ostringstream& fmt);
            std::string plain_request_;
        };

    }
}

#endif // NODE_DB_SQL_STATEMENT_H_
